const dgram = require('node:dgram')

// 通过 dgram 模块创建 UDP 服务，启动在 53 端口，处理开始监听的事件，打印服务器地址和端口，处理错误的事件，打印错误堆栈。收到消息时直接打印。
// 参数的 udp4 代表连接的是 IPv4 的地址
const server = dgram.createSocket('udp4')

function parseHost(msg) {
    // 问题的最开始就是域名，我们只要把域名解析出来就行
    // 我们表示域名是通过 . 来区分，但是存储的时候不是，是通过, 当前域长度 + 当前域内容 + 当前域长度 + 当前域内容 + 当前域长度 + 当前域内容 + 0
    // 以 0 作为域名的结束。

    // 通过 Buffer 的 readUInt8 方法来读取一个无符号整数, 当前域的长度
    let num = msg.readUInt8(0);
    // 从哪里开始。
    let offset = 1;
    let host = "";

    // 直到读到 0，代表域名结束。
    while (num !== 0) {
        // 通过 Buffer 的 subarray 方法来截取某一段内容
        host += msg.subarray(offset, offset + num).toString();
        offset += num;
        num = msg.readUInt8(offset);
        offset += 1;

        if (num !== 0) {
            host += '.'
        }
    }

    return host

}

function forward(msg, rinfo) {
    //  DNS 协议的中转
    // 转发到别的 DNS 服务器，那就是创建一个 UDP 的客户端，把收到的消息传给它，收到消息后再转给客户端
    const client = dgram.createSocket('udp4');
    client.on('message', (fbMsg, fbRinfo) => {
        server.send(fbMsg, rinfo.port, rinfo.address)
        client.close();
    })
    client.send(msg, 53, '219.141.136.10', (err) => {
        if (err) {
            console.log(err)
            client.close()
        }
    })
}

function copyBuffer(src, offset, dst) {
    for (let i = 0; i < src.length; ++i) {
        dst.writeUInt8(src.readUInt8(i), offset + i)
    }
}

function resolve(msg, rinfo) {
    const queryInfo = msg.subarray(12)
    // 用 Buffer.alloc 创建一个 buffer 对象
    const response = Buffer.alloc(28 + queryInfo.length)
    let offset = 0
    // Transaction ID
    const id  = msg.subarray(0, 2)
    copyBuffer(id, 0, response) 
    offset += id.length

    // Flags
    response.writeUInt16BE(0x8180, offset)  
    offset += 2

    // Questions
    response.writeUInt16BE(1, offset)  
    offset += 2

    // Answer RRs
    response.writeUInt16BE(1, offset)  
    offset += 2

    // Authority RRs & Additional RRs
    response.writeUInt32BE(0, offset)  
    offset += 4

    copyBuffer(queryInfo, offset, response)
    offset += queryInfo.length

    // offset to domain name
    response.writeUInt16BE(0xC00C, offset) 
    offset += 2

    const typeAndClass = msg.subarray(msg.length - 4)
    copyBuffer(typeAndClass, offset, response)
    offset += typeAndClass.length

    // TTL, in seconds
    response.writeUInt32BE(600, offset)  
    offset += 4

    // Length of IP
    response.writeUInt16BE(4, offset)  
    offset += 2
    '11.22.33.44'.split('.').forEach(value => {
      response.writeUInt8(parseInt(value), offset)
      offset += 1
    })
    server.send(response, rinfo.port, rinfo.address, (err) => {
      if (err) {
        console.log(err)
        server.close()
      }
    })
}

server.on('message', (msg, rinfo) => {
    // 1. 解析 query 部分
    // 问题前面的部分有 12 个字节，所以我们截取一下再 parse
    // msg 是 Buffer 类型，是 Uint8Array 的子类型，也就是无符号整型。
    // 调用它的 subarray 方法，截取掉前面 12 个字节。
    const host = parseHost(msg.subarray(12))
    console.log(`query: ${host}`)

    if (/guangguangguang/.test(host)) {
        // 自己构造一个 DNS 协议的消息返回
        // 会话 ID 从传过来的 msg 取，flags 也设置下，问题数回答数都是 1，授权数、附加数都是 0。 问题区域和回答区域按照对应的格式来设置
        resolve(msg, rinfo)
    } else {
        forward(msg, rinfo)
    }
});

server.on('error', (err) => {
    console.log(`server error:\n${err.stack}`)
    server.close()
})

server.on('listening', () => {
    const address = server.address()
    console.log(`server listening ${address.address}:${address.port}`)
})

server.bind(53)